package com.campus.help.utils;

import com.campus.help.exception.BusinessException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT工具类
 *
 * @author campus-help
 * @since 2025-09-18
 */
@Slf4j
@Component
public class JwtUtils {

    @Value("${campus.help.jwt.secret:campus-help-jwt-secret-key-2024}")
    private String secret;

    @Value("${campus.help.jwt.expiration:604800}")
    private Long expiration;

    /**
     * 生成JWT token
     *
     * @param studentId 学生ID
     * @param openid 微信openid
     * @return JWT token
     */
    public String generateToken(Long studentId, String openid) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("studentId", studentId);
        claims.put("openid", openid);
        claims.put("type", "student");
        
        return createToken(claims);
    }

    /**
     * 生成管理员JWT token
     *
     * @param adminId 管理员ID
     * @param username 管理员用户名
     * @param role 角色
     * @return JWT token
     */
    public String generateAdminToken(Long adminId, String username, Integer role) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("adminId", adminId);
        claims.put("username", username);
        claims.put("role", role);
        claims.put("type", "admin");
        
        return createToken(claims);
    }

    /**
     * 生成商户JWT token
     *
     * @param merchantId 商户ID
     * @param username 商户用户名
     * @return JWT token
     */
    public String generateMerchantToken(Long merchantId, String username) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("merchantId", merchantId);
        claims.put("username", username);
        claims.put("type", "merchant");
        
        return createToken(claims);
    }

    /**
     * 创建token
     */
    private String createToken(Map<String, Object> claims) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration * 1000);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 验证token
     *
     * @param token JWT token
     * @return token信息
     */
    public TokenInfo validateToken(String token) {
        try {
            log.debug("开始验证JWT token: {}", token);
            
            if (token == null || token.trim().isEmpty()) {
                log.warn("Token为空");
                throw new BusinessException("Token不能为空");
            }
            
            Claims claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();

            // 将Claims转换为Map
            Map<String, Object> claimsMap = new HashMap<>();
            claims.forEach(claimsMap::put);

            String type = claims.get("type", String.class);
            log.debug("JWT解析成功, type: {}", type);

            if ("admin".equals(type)) {
                Long adminId = claims.get("adminId", Long.class);
                String username = claims.get("username", String.class);
                if (adminId == null || username == null) {
                    log.warn("管理员Token中缺少必要信息, adminId: {}, username: {}", adminId, username);
                    throw new BusinessException("Token无效");
                }
            } else if ("merchant".equals(type)) {
                Long merchantId = claims.get("merchantId", Long.class);
                String username = claims.get("username", String.class);
                if (merchantId == null || username == null) {
                    log.warn("商户Token中缺少必要信息, merchantId: {}, username: {}", merchantId, username);
                    throw new BusinessException("Token无效");
                }
            } else {
                Long studentId = claims.get("studentId", Long.class);
                String openid = claims.get("openid", String.class);
                if (studentId == null || openid == null) {
                    log.warn("学生Token中缺少必要信息, studentId: {}, openid: {}", studentId, openid);
                    throw new BusinessException("Token无效");
                }
            }

            return new TokenInfo(claimsMap);
        } catch (io.jsonwebtoken.ExpiredJwtException e) {
            log.warn("Token已过期: {}", e.getMessage());
            throw new BusinessException("Token已过期");
        } catch (io.jsonwebtoken.MalformedJwtException e) {
            log.warn("Token格式错误: {}", e.getMessage());
            throw new BusinessException("Token格式错误");
        } catch (io.jsonwebtoken.SignatureException e) {
            log.warn("Token签名验证失败: {}", e.getMessage());
            throw new BusinessException("Token签名验证失败");
        } catch (Exception e) {
            log.error("Token验证失败, token: {}, 错误信息: {}", token, e.getMessage(), e);
            throw new BusinessException("Token验证失败: " + e.getMessage());
        }
    }

    /**
     * 从token中获取学生ID
     */
    public Long getStudentIdFromToken(String token) {
        TokenInfo tokenInfo = validateToken(token);
        return tokenInfo.getStudentId();
    }

    /**
     * 从token中获取openid
     */
    public String getOpenidFromToken(String token) {
        TokenInfo tokenInfo = validateToken(token);
        return tokenInfo.getOpenid();
    }

    /**
     * 判断token是否过期
     */
    public boolean isTokenExpired(String token) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
            return claims.getExpiration().before(new Date());
        } catch (Exception e) {
            return true;
        }
    }

    /**
     * Token信息类
     */
    public static class TokenInfo {
        private Long studentId;
        private String openid;
        private String type;
        private Long adminId;
        private Long merchantId;
        private String username;
        private Integer role;
        private Map<String, Object> claims;

        public TokenInfo(Long studentId, String openid, String type) {
            this.studentId = studentId;
            this.openid = openid;
            this.type = type;
        }

        public TokenInfo(Map<String, Object> claims) {
            this.claims = claims;
            this.type = (String) claims.get("type");
            if ("admin".equals(this.type)) {
                this.adminId = Long.valueOf(claims.get("adminId").toString());
                this.username = (String) claims.get("username");
                this.role = (Integer) claims.get("role");
            } else if ("merchant".equals(this.type)) {
                this.merchantId = Long.valueOf(claims.get("merchantId").toString());
                this.username = (String) claims.get("username");
            } else {
                this.studentId = Long.valueOf(claims.get("studentId").toString());
                this.openid = (String) claims.get("openid");
            }
        }

        // getter和setter方法
        public Long getStudentId() { return studentId; }
        public void setStudentId(Long studentId) { this.studentId = studentId; }
        public String getOpenid() { return openid; }
        public void setOpenid(String openid) { this.openid = openid; }
        public String getType() { return type; }
        public void setType(String type) { this.type = type; }
        public Long getAdminId() { return adminId; }
        public void setAdminId(Long adminId) { this.adminId = adminId; }
        public Long getMerchantId() { return merchantId; }
        public void setMerchantId(Long merchantId) { this.merchantId = merchantId; }
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public Integer getRole() { return role; }
        public void setRole(Integer role) { this.role = role; }
        public Map<String, Object> getClaims() { return claims; }
        public void setClaims(Map<String, Object> claims) { this.claims = claims; }
    }
}
